﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace BizControl.Presentation.Infrastructure.Controls.StarRating
{
  /// <summary>
  /// Interaction logic for StarRatingControl.xaml
  /// </summary>
  public partial class StarRatingControl : UserControl
  {
    private const Int32 DEFAULT_MINIMUM = 0;
    private const Int32 DEFAULT_MAXIMUM = 5;
    private const Decimal DEFAULT_STEP = 0.1m;

    public StarRatingControl()
    {
      InitializeComponent();

      this.spStars.MouseEnter += new MouseEventHandler(StarRatingControl_MouseEnter);
      this.spStars.MouseMove += new MouseEventHandler(StarRatingControl_MouseMove);
      this.spStars.MouseLeave += new MouseEventHandler(StarRatingControl_MouseLeave);
      this.spStars.MouseLeftButtonDown += new MouseButtonEventHandler(StarRatingControl_MouseLeftButtonDown);
    }

    void StarRatingControl_MouseEnter(object sender, MouseEventArgs e)
    {
      var mousePos = e.GetPosition(this.spStars);
      HandleMouseOver(mousePos);
    }

    private Decimal GetValueFromPosition(Point mousePos)
    {
      if (this.spStars.Children.Count == 0) return 0.0m;
      double starRatingWidth = this.spStars.ActualWidth;
      double percent = mousePos.X / starRatingWidth;
      Decimal value = Round(this, (Decimal)percent * Maximum);
      if (value < Minimum) value = Minimum;
      if (value > Maximum) value = Maximum;
      return value;
    }

    void StarRatingControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
      if (this.IsEnabled)
      {
        this.Value = GetValueFromPosition(e.GetPosition(this.spStars));
      }
    }

    void StarRatingControl_MouseLeave(object sender, MouseEventArgs e)
    {
      IsHovering = false;
      HoverValue = Value;
      UpdateStars(this);
    }

    private bool IsInBounds(Point p)
    {
      double maxX = this.spStars.ActualWidth;
      double maxY = this.spStars.ActualHeight;
      return (p.Y >= 0) &&
          (p.Y < maxY) &&
          (p.X >= 0) &&
          (p.X < maxX);
    }

    void StarRatingControl_MouseMove(object sender, MouseEventArgs e)
    {
      var mousePos = e.GetPosition(spStars);
      HandleMouseOver(mousePos);
    }

    private void HandleMouseOver(Point mousePos)
    {
      if (this.IsEnabled) // no hover ratings if not enabled
      {
        this.IsHovering = IsInBounds(mousePos);
        if (this.IsHovering)
        {
          this.HoverValue = GetValueFromPosition(mousePos);
        }
      }
    }
    
    public static readonly DependencyProperty IsHoveringProperty =
      DependencyProperty.Register("IsHovering", typeof(bool),
        typeof(StarRatingControl),
        new FrameworkPropertyMetadata(false));

    public bool IsHovering
    {
      get { return (bool)GetValue(IsHoveringProperty); }
      set { SetValue(IsHoveringProperty, value); }
    }

    #region BackgroundColor

    /// <summary>
    /// BackgroundColor Dependency Property
    /// </summary>
    public static readonly DependencyProperty BackgroundColorProperty =
        DependencyProperty.Register("BackgroundColor", typeof(SolidColorBrush),
          typeof(StarRatingControl),
          new FrameworkPropertyMetadata((SolidColorBrush)Brushes.Transparent,
          new PropertyChangedCallback(OnBackgroundColorChanged)));

    /// <summary>
    /// Gets or sets the BackgroundColor property.  
    /// </summary>
    public SolidColorBrush BackgroundColor
    {
      get { return (SolidColorBrush)GetValue(BackgroundColorProperty); }
      set { SetValue(BackgroundColorProperty, value); }
    }

    /// <summary>
    /// Handles changes to the BackgroundColor property.
    /// </summary>
    private static void OnBackgroundColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      StarRatingControl control = (StarRatingControl)d;
      foreach (StarControl star in control.spStars.Children)
        star.BackgroundColor = (SolidColorBrush)e.NewValue;
    }

    #endregion

    #region StarForegroundColor

    /// <summary>
    /// StarForegroundColor Dependency Property
    /// </summary>
    public static readonly DependencyProperty StarForegroundColorProperty =
        DependencyProperty.Register("StarForegroundColor", typeof(SolidColorBrush),
          typeof(StarRatingControl),
          new FrameworkPropertyMetadata((SolidColorBrush)Brushes.Transparent,
          new PropertyChangedCallback(OnStarForegroundColorChanged)));

    /// <summary>
    /// Gets or sets the StarForegroundColor property.  
    /// </summary>
    public SolidColorBrush StarForegroundColor
    {
      get { return (SolidColorBrush)GetValue(StarForegroundColorProperty); }
      set { SetValue(StarForegroundColorProperty, value); }
    }

    /// <summary>
    /// Handles changes to the StarForegroundColor property.
    /// </summary>
    private static void OnStarForegroundColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      StarRatingControl control = (StarRatingControl)d;
      foreach (StarControl star in control.spStars.Children)
        star.StarForegroundColor = (SolidColorBrush)e.NewValue;

    }
    #endregion

    #region StarOutlineColor

    /// <summary>
    /// StarOutlineColor Dependency Property
    /// </summary>
    public static readonly DependencyProperty StarOutlineColorProperty =
        DependencyProperty.Register("StarOutlineColor", typeof(SolidColorBrush),
          typeof(StarRatingControl),
          new FrameworkPropertyMetadata((SolidColorBrush)Brushes.Transparent,
          new PropertyChangedCallback(OnStarOutlineColorChanged)));

    /// <summary>
    /// Gets or sets the StarOutlineColor property.  
    /// </summary>
    public SolidColorBrush StarOutlineColor
    {
      get { return (SolidColorBrush)GetValue(StarOutlineColorProperty); }
      set { SetValue(StarOutlineColorProperty, value); }
    }

    /// <summary>
    /// Handles changes to the StarOutlineColor property.
    /// </summary>
    private static void OnStarOutlineColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      StarRatingControl control = (StarRatingControl)d;
      foreach (StarControl star in control.spStars.Children)
        star.StarOutlineColor = (SolidColorBrush)e.NewValue;
    }

    #endregion

    #region Value

    /// <summary>
    /// Value Dependency Property
    /// </summary>
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(Decimal),
          typeof(StarRatingControl),
          new FrameworkPropertyMetadata((Decimal)0.0,
          new PropertyChangedCallback(OnValueChanged),
          new CoerceValueCallback(CoerceValueValue)));

    /// <summary>
    /// Gets or sets the Value property.  
    /// </summary>
    public Decimal Value
    {
      get { return (Decimal)GetValue(ValueProperty); }
      set { SetValue(ValueProperty, value); }
    }

    /// <summary>
    /// Handles changes to the Value property.
    /// </summary>
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MinimumProperty);
      d.CoerceValue(MaximumProperty);
      StarRatingControl ratingsControl = (StarRatingControl)d;
      UpdateStars(ratingsControl);
    }

    /// <summary>
    /// Coerces the Value value.
    /// </summary>
    private static object CoerceValueValue(DependencyObject d, object value)
    {
      StarRatingControl ratingsControl = (StarRatingControl)d;
      Decimal current = Round(ratingsControl, (Decimal)value);
      if (current < ratingsControl.Minimum) current = ratingsControl.Minimum;
      if (current > ratingsControl.Maximum) current = ratingsControl.Maximum;
      return current;
    }

    #endregion

    #region HoverValue

    /// <summary>
    /// Value Dependency Property
    /// </summary>
    public static readonly DependencyProperty HoverValueProperty =
        DependencyProperty.Register("HoverValue", typeof(Decimal),
          typeof(StarRatingControl),
          new FrameworkPropertyMetadata((Decimal)0.0,
          new PropertyChangedCallback(OnHoverValueChanged),
          new CoerceValueCallback(CoerceValueHoverValue)));

    /// <summary>
    /// Gets or sets the HoverValue property.  
    /// </summary>
    public Decimal HoverValue
    {
      get { return (Decimal)GetValue(HoverValueProperty); }
      set { SetValue(HoverValueProperty, value); }
    }

    /// <summary>
    /// Handles changes to the Value property.
    /// </summary>
    private static void OnHoverValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MinimumProperty);
      d.CoerceValue(MaximumProperty);
      StarRatingControl ratingsControl = (StarRatingControl)d;
      if (ratingsControl.IsHovering)
        UpdateStars(ratingsControl);
    }

    /// <summary>
    /// Coerces the Value value.
    /// </summary>
    private static object CoerceValueHoverValue(DependencyObject d, object value)
    {
      StarRatingControl ratingsControl = (StarRatingControl)d;
      Decimal current = Round(ratingsControl, (Decimal)value);
      if (current < ratingsControl.Minimum) current = ratingsControl.Minimum;
      if (current > ratingsControl.Maximum) current = ratingsControl.Maximum;
      return current;
    }

    #endregion

    #region NumberOfStars

    /// <summary>
    /// NumberOfStars Dependency Property
    /// </summary>
    public static readonly DependencyProperty NumberOfStarsProperty =
        DependencyProperty.Register("NumberOfStars", typeof(Int32), typeof(StarRatingControl),
          new FrameworkPropertyMetadata((Int32)5,
          new PropertyChangedCallback(OnNumberOfStarsChanged),
          new CoerceValueCallback(CoerceNumberOfStarsValue)));

    /// <summary>
    /// Gets or sets the NumberOfStars property.  
    /// </summary>
    public Int32 NumberOfStars
    {
      get { return (Int32)GetValue(NumberOfStarsProperty); }
      set { SetValue(NumberOfStarsProperty, value); }
    }

    /// <summary>
    /// Handles changes to the NumberOfStars property.
    /// </summary>
    private static void OnNumberOfStarsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MinimumProperty);
      d.CoerceValue(MaximumProperty);
      StarRatingControl ratingsControl = (StarRatingControl)d;
      CreateStars(ratingsControl);
    }

    /// <summary>
    /// Coerces the NumberOfStars value.
    /// </summary>
    private static object CoerceNumberOfStarsValue(DependencyObject d, object value)
    {
      StarRatingControl ratingsControl = (StarRatingControl)d;
      Int32 current = (Int32)value;
      if (current < ratingsControl.Minimum) current = ratingsControl.Minimum;
      if (current > ratingsControl.Maximum) current = ratingsControl.Maximum;
      return current;
    }

    #endregion

    #region Maximum

    /// <summary>
    /// Maximum Dependency Property
    /// </summary>
    public static readonly DependencyProperty MaximumProperty =
        DependencyProperty.Register("Maximum", typeof(Int32), typeof(StarRatingControl), new FrameworkPropertyMetadata(DEFAULT_MAXIMUM));

    /// <summary>
    /// Gets or sets the Maximum property.  
    /// </summary>
    public Int32 Maximum
    {
      get { return (Int32)GetValue(MaximumProperty); }
      set { SetValue(MaximumProperty, value); }
    }

    #endregion

    #region Minimum

    /// <summary>
    /// Minimum Dependency Property
    /// </summary>
    public static readonly DependencyProperty MinimumProperty =
        DependencyProperty.Register("Minimum", typeof(Int32), typeof(StarRatingControl),
            new FrameworkPropertyMetadata(DEFAULT_MINIMUM));

    /// <summary>
    /// Gets or sets the Minimum property.  
    /// </summary>
    public Int32 Minimum
    {
      get { return (Int32)GetValue(MinimumProperty); }
      set { SetValue(MinimumProperty, value); }
    }

    #endregion

    #region Epsilon

    /// <summary>
    /// Minimum Dependency Property
    /// </summary>
    public static readonly DependencyProperty StepProperty =
        DependencyProperty.Register("Step", typeof(Decimal), typeof(StarRatingControl),
            new FrameworkPropertyMetadata(DEFAULT_STEP));

    /// <summary>
    /// Gets or sets the Minimum property.  
    /// </summary>
    public Decimal Step
    {
      get { return (Decimal)GetValue(StepProperty); }
      set { SetValue(StepProperty, value); }
    }

    #endregion

    #region Private Helpers
    /// <summary>
    /// Sets up stars when Value or NumberOfStars properties change
    /// Will only show up to the number of stars requested (up to Maximum)
    /// so if Value > NumberOfStars * 1, then Value is clipped to maximum
    /// number of full stars
    /// </summary>
    /// <param name="ratingsControl"></param>
    private static void CreateStars(StarRatingControl ratingsControl)
    {
      ratingsControl.spStars.Children.Clear();
      for (int i = 0; i < ratingsControl.NumberOfStars; i++)
      {
        StarControl star = new StarControl();
        ratingsControl.spStars.Children.Insert(i, star);
      }
      UpdateStars(ratingsControl);
    }

    private static void UpdateStars(StarRatingControl ratingsControl)
    {
      if (ratingsControl.spStars.Children.Count != ratingsControl.NumberOfStars)
        CreateStars(ratingsControl);
      Decimal localValue;
      if (ratingsControl.IsHovering)
        localValue = ratingsControl.HoverValue;
      else
        localValue = ratingsControl.Value;

      for (int i = 0; i < ratingsControl.NumberOfStars; i++)
      {
        StarControl star = (StarControl)(ratingsControl.spStars.Children[i]);
        UpdateStar(star, ratingsControl, localValue);
        localValue -= ratingsControl.Maximum / ratingsControl.NumberOfStars;
      }
    }

    private static void UpdateStar(StarControl star, StarRatingControl ratingsControl, Decimal value)
    {
      star.BackgroundColor = ratingsControl.BackgroundColor;
      star.StarForegroundColor = ratingsControl.StarForegroundColor;
      star.StarOutlineColor = ratingsControl.StarOutlineColor;
      if (value > ratingsControl.Maximum / ratingsControl.NumberOfStars)
        star.Value = ratingsControl.Maximum / ratingsControl.NumberOfStars;
      else if (value > 0)
        star.Value = value;
      else
        star.Value = 0.0m;
    }

    private static Decimal Round(StarRatingControl ratingsControl, Decimal value)
    {
      Decimal intPart = (Decimal)((Int32)value);
      Decimal decimalPart = value - intPart;
      Decimal step = ratingsControl.Step;
      int parts = (int)(decimalPart / step);
      if (value - (intPart + (step * parts)) < step / 2.0m)
        return intPart + (step * parts);
      else
        return intPart + (step * (parts + 1));
    }

    #endregion
  }
}